#pragma once

#include "ArBuild.hpp"
#include "ArMath.hpp"

namespace Aria
{
	template<typename CharT>
	class CharTraits;

	template<>
	class CharTraits<AChar>
	{
	public:
		static NativeUInt GetLength(const AChar *text)
		{
			return strlen(text);
		}
	};

	template<>
	class CharTraits<UChar>
	{
	public:
		static NativeUInt GetLength(const UChar *text)
		{
			return wcslen(text);
		}
	};

	template<typename CharT, typename CharTraitsT = CharTraits<CharT> >
	class StringBuffer
	{
	public:
		typedef CharT CharType;
		typedef CharTraitsT CharTraitsType;
	public:
		static CharT* CreateCopy(const CharT *source, NativeUInt length, NativeUInt minCapacity = 0)
		{
			return _GetTextFromBuffer(_CreateCopy(source, length, minCapacity));
		}

		static CharT* CreateAppended(CharT *target, const CharT *source, NativeUInt length)
		{
			return _GetTextFromBuffer(_CreateAppended(_GetBufferFromText(target), source, length));
		}

		static CharT* GetEmpty()
		{
			return _GetTextFromBuffer(_GetEmpty());
		}

		//////////////////////////////////////////////////////////////////////////

		static NativeUInt GetCapacity(CharT *text)
		{
			return _GetCapacity(_GetBufferFromText(text));
		}

		static NativeUInt GetLength(CharT *text)
		{
			return _GetLength(_GetBufferFromText(text));
		}

		static CharT* SetLength(CharT *text, NativeUInt length)
		{
			return _GetTextFromBuffer(_SetLength(_GetBufferFromText(text), length));
		}

		//////////////////////////////////////////////////////////////////////////

		static void AddRef(CharT *text)
		{
			_AddRef(_GetBufferFromText(text));
		}

		static void Release(CharT *text)
		{
			_Release(_GetBufferFromText(text));
		}

		static CharT* MakeUnique(CharT *text, NativeUInt minCapacity)
		{
			return _GetTextFromBuffer(_MakeUnique(_GetBufferFromText(text), minCapacity));
		}
	private:
		static const NativeUInt MinCapacity = sizeof(CharT) == 1 ? 16 : sizeof(CharT) == 2 ? 8 : sizeof(CharT) == 4 ? 4 : 4;
	private:
		static StringBuffer* _GetBufferFromText(CharT *text)
		{
			return reinterpret_cast<StringBuffer *>(reinterpret_cast<NativeUInt>(text) - ArOffsetOf(StringBuffer, m_text));
		}

		static CharT* _GetTextFromBuffer(StringBuffer *buffer)
		{
			return buffer->m_text;
		}

		//////////////////////////////////////////////////////////////////////////

		static NativeUInt _CalcNewCapacity(NativeUInt oldCapacity, NativeUInt requiredLength)
		{
			if (oldCapacity >= requiredLength)
				return oldCapacity;
			
			if (oldCapacity == 0)
				return Max(requiredLength, MinCapacity);
			else
				return Max(requiredLength, oldCapacity * 3 / 2);
		}

		//////////////////////////////////////////////////////////////////////////

		static StringBuffer* _CreateUninitialized(NativeUInt capacity)
		{
			capacity = _CalcNewCapacity(0, capacity);
			StringBuffer *buffer = reinterpret_cast<StringBuffer *>(new char[sizeof(StringBuffer) + capacity * sizeof(CharT)]);
			buffer->m_refCount = 1;
			buffer->m_capacity = capacity;
			return buffer;
		}

		static StringBuffer* _CreateCopy(const CharT *source, NativeUInt length, NativeUInt minCapacity = 0)
		{
			StringBuffer *buffer = _CreateUninitialized(_CalcNewCapacity(0, Max(length, minCapacity)));
			if (length > 0)
				memcpy(&buffer->m_text[0], source, length * sizeof(CharT));
			buffer->m_text[length] = 0;
			buffer->m_length = length;
			return buffer;
		}

		static StringBuffer* _CreateAppended(StringBuffer *target, const CharT *source, NativeUInt length)
		{
			if (source == NULL || length == 0)
				return target;

			NativeUInt newLength = target->m_length + length;
			NativeUInt newCapacity = _CalcNewCapacity(target->m_capacity, newLength);
			StringBuffer *newBuffer = _MakeUnique(target, newCapacity);
			memcpy(&newBuffer->m_text[newBuffer->m_length], source, length * sizeof(CharT));
			newBuffer->m_text[newLength] = 0;
			newBuffer->m_length = newLength;
			return newBuffer;
		}

		static StringBuffer* _GetEmpty()
		{
			static StringBuffer empty;
			return &empty;
		}

		//////////////////////////////////////////////////////////////////////////

		static NativeUInt _GetCapacity(StringBuffer *buffer)
		{
			return buffer->m_capacity;
		}

		static NativeUInt _GetLength(StringBuffer *buffer)
		{
			return buffer->m_length;
		}

		static StringBuffer* _SetLength(StringBuffer *buffer, NativeUInt length)
		{
			StringBuffer *newBuffer = _CreateCopy(buffer->m_text, length);
			_Release(buffer);
			return newBuffer;
		}

		//////////////////////////////////////////////////////////////////////////

		static void _AddRef(StringBuffer *buffer)
		{
			++buffer->m_refCount;
		}

		static void _Release(StringBuffer *buffer)
		{
			if (--buffer->m_refCount == 0 && buffer != _GetEmpty())
				delete[] reinterpret_cast<char *>(buffer);
		}

		static StringBuffer* _MakeUnique(StringBuffer *buffer, NativeUInt minCapacity = 0)
		{
			if (buffer->m_refCount <= 1 && buffer->m_capacity >= minCapacity)
				return buffer;

			StringBuffer *newBuffer = _CreateCopy(buffer->m_text, buffer->m_length, minCapacity);
			_Release(buffer);
			return newBuffer;
		}

		//////////////////////////////////////////////////////////////////////////

		StringBuffer()
		{
			m_refCount = 0x7FFFFFFF;
			m_capacity = 0;
			m_length = 0;
			m_text[0] = 0;
		}
	private:
		NativeUInt m_refCount;
		NativeUInt m_capacity;
		NativeUInt m_length;
		CharT m_text[1];
	};

	template<typename CharT, typename CharTraitsT = CharTraits<CharT> >
	class StringT
	{
	public:
		typedef CharT Char;
		typedef CharTraitsT CharTraits;
		typedef StringBuffer<CharT, CharTraitsT> StringBuffer;
	public:
		StringT()
		{
			m_text = StringBuffer::GetEmpty();
		}

		StringT(const StringT &source)
		{
			_Init(source);
		}

		StringT(const CharT *source, NativeUInt length = 0)
		{
			if (length == 0)
				length = CharTraits::GetLength(source);
			_Init(source, length, 0);
		}

		//////////////////////////////////////////////////////////////////////////

		~StringT()
		{
			StringBuffer::Release(m_text);
		}

		//////////////////////////////////////////////////////////////////////////

		void Assign(const StringT &source)
		{
			StringBuffer::Release(m_text);
			StringBuffer::AddRef(source.m_text);
			m_text = source.m_text;
		}

		void Assign(const CharT *source)
		{
			StringBuffer::Release(m_text);
			NativeUInt length = CharTraits::GetLength(source);
			m_text = StringBuffer::CreateCopy(source, length);
		}

		StringT& operator =(const StringT &right)
		{
			Assign(other);
			return *this;
		}

		StringT& operator =(const CharT *right)
		{
			Assign(other);
			return *this;
		}

		//////////////////////////////////////////////////////////////////////////

		void Append(const StringT &source)
		{
			m_text = StringBuffer::CreateAppended(m_text, source, source.GetLength());
		}

		void Append(const CharT *source, NativeUInt length = 0)
		{
			if (length == 0)
				length = CharTraits::GetLength(source);
			m_text = StringBuffer::CreateAppended(m_text, source, length);
		}

		StringT& operator +=(const StringT &right)
		{
			Append(right);
			return *this;
		}

		StringT& operator +=(const CharT *right)
		{
			Append(right);
			return *this;
		}

		friend StringT operator +(const StringT &left, const StringT &right)
		{
			return StringT(left) += right;
		}
 
		friend StringT operator +(const StringT &left, const CharT *right)
		{
			NativeUInt rightLength = CharTraits::GetLength(right);
			StringT result(left, left.GetLength(), left.GetLength() + rightLength);
			result.Append(right, rightLength);
			return result;
		}

		friend StringT operator +(const CharT *left, const StringT &right)
		{
			NativeUInt leftLength = CharTraits::GetLength(left);
			StringT result(left, leftLength, leftLength + right.GetLength());
			result.Append(right);
			return result;
		}

		//////////////////////////////////////////////////////////////////////////

		NativeUInt GetCapacity() const
		{
			return StringBuffer::GetCapacity(m_text);
		}

		NativeUInt GetLength() const
		{
			return StringBuffer::GetLength(m_text);
		}

		const CharT* GetString() const
		{
			return m_text;
		}

		operator const CharT *() const
		{
			return GetString();
		}
		
		//////////////////////////////////////////////////////////////////////////

		CharT* GetBuffer(NativeUInt capacity = 0)
		{
			m_text = StringBuffer::MakeUnique(m_text, capacity);
			return m_text;
		}

		void ReleaseBuffer(NativeUInt length = 0)
		{
			if (length == 0)
				length = CharTraits::GetLength(m_text);
			m_text = StringBuffer::SetLength(m_text, length);
		}
	private:
		StringT(const CharT *source, NativeUInt length, NativeUInt minCapacity)
		{
			_Init(source, length, minCapacity);
		}

		void _Init(const StringT &source)
		{
			StringBuffer::AddRef(source.m_text);
			m_text = source.m_text;
		}

		void _Init(const CharT *source, NativeUInt length, NativeUInt minCapacity)
		{
			m_text = StringBuffer::CreateCopy(source, length, minCapacity);
		}
	private:
		CharT *m_text;
	};

	typedef StringT<AChar> StringA;
	typedef StringT<UChar> StringU;
}
